import * as http from 'node:http';
import * as https from 'node:https';
import * as qs from 'node:querystring';

export namespace Http {
  export type Headers = Record<string, number | string | string[]>;
  export interface IFetchOpts<P extends unknown = any, J extends unknown = any> extends Omit<https.RequestOptions, 'method' | 'headers'> {
    headers?: Headers;
    method?: 'GET' | 'POST' | 'DELETE' | 'PUT';
    json?: J;
    search?: P;
  };

  export function protocol(url: string): string {
    return url.substring(0, url.indexOf('//'));
  }

  export class FetchResult {
    private _raw: string;
    raw() { return this._raw; }
    json<T>() {
      return <T>JSON.parse(this._raw);
    }
    constructor(raw: string) {
      this._raw = raw;
    }
  }

  export function fetch<P extends unknown = any, J extends unknown = any>(url: string, options?: IFetchOpts<P, J>): Promise<FetchResult> {
    const { request } = 'https:' === protocol(url) ? https : http;
    const opts: https.RequestOptions = options || { method: 'GET' };
    const data = options?.json ? JSON.stringify(options.json) : '';
    const headers: http.OutgoingHttpHeaders = { ...options?.headers };
    if (options?.json) {
      headers['Content-type'] = "application/json";
      headers['Content-Length'] = data.length;
    }
    opts.headers = headers;

    let fullUrl = url;
    if (options?.search) {
      fullUrl += (
        fullUrl.indexOf('?') < 0 ? '?' : '&'
      ) + qs.stringify(options.search, '&', '=', {
        encodeURIComponent
      });
    }
    
    return new Promise((resolve, reject) => {
      const req = request(fullUrl, opts, resp => {
        let data = '';
        resp.on('data', chunk => data += chunk);
        resp.on('end', () => resolve(new FetchResult(data)));
      }).on('error', (err) => reject(err));
      req.write(data);
      req.end();
    });
  }
}